<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_26</span></h2>

  <p>// Call all the listener methods:</p>

  <p>for(int i = 0; i &lt; lv.size(); i++) {</p>

  <p>ActionListener al =</p>

  <p>(ActionListener)lv.elementAt(i);</p>

  <p>al.actionPerformed(a);</p>

  <p>}</p>

  <p>}</p>

  <p>class ML extends MouseAdapter {</p>

  <p>public void mousePressed(MouseEvent e) {</p>

  <p>Graphics g = getGraphics();</p>

  <p>g.setColor(tColor);</p>

  <p>g.setFont(</p>

  <p>new Font(</p>

  <p>"TimesRoman", Font.BOLD, fontSize));</p>

  <p>int width =</p>

  <p>g.getFontMetrics().stringWidth(text);</p>

  <p>g.drawString(text,</p>

  <p>(getSize().width - width) /2,</p>

  <p>getSize().height/2);</p>

  <p>g.dispose();</p>

  <p>notifyListeners();</p>

  <p>}</p>

  <p>}</p>

  <p>class MM extends MouseMotionAdapter {</p>

  <p>public void mouseMoved(MouseEvent e) {</p>

  <p>xm = e.getX();</p>

  <p>ym = e.getY();</p>

  <p>repaint();</p>

  <p>}</p>

  <p>}</p>

  <p>// Testing the BangBean2:</p>

  <p>public static void main(String[] args) {</p>

  <p>BangBean2 bb = new BangBean2();</p>

  <p>bb.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>System.out.println("ActionEvent" + e);</p>

  <p>}</p>

  <p>});</p>

  <p>bb.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>System.out.println("BangBean2 action");</p>

  <p>}</p>

  <p>});</p>

  <p>bb.addActionListener(new ActionListener() {</p>

  <p>public void actionPerformed(ActionEvent e){</p>

  <p>System.out.println("More action");</p>

  <p>}</p>

  <p>});</p>

  <p>Frame aFrame = new Frame("BangBean2 Test");</p>

  <p>aFrame.addWindowListener(new WindowAdapter(){</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(bb, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,300);</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>很容易就可以为方法添加synchronized。但注意在addActionListener()和removeActionListener()中，现在添加了ActionListener，并从一个Vector中移去，所以能够根据自己愿望使用任意多个。</p>

  <p>我们注意到，notifyListeners()方法并未设为“同步”。可从多个线程中发出对这个方法的调用。另外，在对notifyListeners()调用的中途，也可能发出对addActionListener()和removeActionListener()的调用。这显然会造成问题，因为它否定了Vector actionListeners。为缓解这个问题，我们在一个synchronized从句中“克隆”了Vector，并对克隆进行了否定。这样便可在不影响notifyListeners()的前提下，对Vector进行操纵。</p>

  <p>paint()方法也没有设为“同步”。与单纯地添加自己的方法相比，决定是否对过载的方法进行同步要困难得多。在这个例子中，无论paint()是否“同步”，它似乎都能正常地工作。但必须考虑的问题包括：</p>

  <p>(1) 方法会在对象内部修改“关键”变量的状态吗？为判断一个变量是否“关键”，必须知道它是否会被程序中的其他线程读取或设置（就目前的情况看，读取或设置几乎肯定是通过“同步”方法进行的，所以可以只对它们进行检查）。对paint()的情况来说，不会发生任何修改。</p>

  <p>(2) 方法要以这些“关键”变量的状态为基础吗？如果一个“同步”方法修改了一个变量，而我们的方法要用到这个变量，那么一般都愿意把自己的方法也设为“同步”。基于这一前提，大家可观察到cSize由“同步”方法进行了修改，所以paint()应当是“同步”的。但在这里，我们可以问：“假如cSize在paint()执行期间发生了变化，会发生的最糟糕的事情是什么呢？”如果发现情况不算太坏，而且仅仅是暂时的效果，那么最好保持paint()的“不同步”状态，以避免同步方法调用带来的额外开销。</p>

  <p>(3) 要留意的第三条线索是paint()基础类版本是否“同步”，在这里它不是同步的。这并不是一个非常严格的参数，仅仅是一条“线索”。比如在目前的情况下，通过同步方法（好cSize）改变的一个字段已合成到paint()公式里，而且可能已改变了情况。但请注意，synchronized不能继承――也就是说，假如一个方法在基础类中是“同步”的，那么在衍生类过载版本中，它不会自动进入“同步”状态。</p>

  <p>TestBangBean2中的测试代码已在前一章的基础上进行了修改，已在其中加入了额外的“听众”，从而演示了BangBean2的多造型能力。</p>

  <p>14.3 堵塞</p>

  <p>一个线程可以有四种状态：</p>

  <p>(1) 新（New）：线程对象已经创建，但尚未启动，所以不可运行。</p>

  <p>(2) 可运行（Runnable）：意味着一旦时间分片机制有空闲的CPU周期提供给一个线程，那个线程便可立即开始运行。因此，线程可能在、也可能不在运行当中，但一旦条件许可，没有什么能阻止它的运行――它既没有“死”掉，也未被“堵塞”。</p>

  <p>(3) 死（Dead）：从自己的run()方法中返回后，一个线程便已“死”掉。亦可调用stop()令其死掉，但会产生一个违例――属于Error的一个子类（也就是说，我们通常不捕获它）。记住一个违例的“掷”出应当是一个特殊事件，而不是正常程序运行的一部分。所以不建议你使用stop()（在Java 1.2则是坚决反对）。另外还有一个destroy()方法（它永远不会实现），应该尽可能地避免调用它，因为它非常武断，根本不会解除对象的锁定。</p>

  <p>(4) 堵塞（Blocked）：线程可以运行，但有某种东西阻碍了它。若线程处于堵塞状态，调度机制可以简单地跳过它，不给它分配任何CPU时间。除非线程再次进入“可运行”状态，否则不会采取任何操作。</p>

  <p>14.3.1 为何会堵塞</p>

  <p>堵塞状态是前述四种状态中最有趣的，值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的：</p>

  <p>(1) 调用sleep(毫秒数)，使线程进入“睡眠”状态。在规定的时间内，这个线程是不会运行的。</p>

  <p>(2) 用suspend()暂停了线程的执行。除非线程收到resume()消息，否则不会返回“可运行”状态。</p>

  <p>(3) 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息，否则不会变成“可运行”（是的，这看起来同原因2非常相象，但有一个明显的区别是我们马上要揭示的）。</p>

  <p>(4) 线程正在等候一些IO（输入输出）操作完成。</p>

  <p>(5) 线程试图调用另一个对象的“同步”方法，但那个对象处于锁定状态，暂时无法使用。</p>

  <p>亦可调用yield()（Thread类的一个方法）自动放弃CPU，以便其他线程能够运行。然而，假如调度机制觉得我们的线程已拥有足够的时间，并跳转到另一个线程，就会发生同样的事情。也就是说，没有什么能防止调度机制重新启动我们的线程。线程被堵塞后，便有一些原因造成它不能继续运行。</p>

  <p>下面这个例子展示了进入堵塞状态的全部五种途径。它们全都存在于名为Blocking.java的一个文件中，但在这儿采用散落的片断进行解释（大家可注意到片断前后的“Continued”以及“Continuing”标志。利用第17章介绍的工具，可将这些片断连结到一起）。首先让我们看看基本的框架：</p>

  <p>//: Blocking.java</p>

  <p>// Demonstrates the various ways a thread</p>

  <p>// can be blocked.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>import java.io.*;</p>

  <p>//////////// The basic framework ///////////</p>

  <p>class Blockable extends Thread {</p>

  <p>private Peeker peeker;</p>

  <p>protected TextField state = new TextField(40);</p>

  <p>protected int i;</p>

  <p>public Blockable(Container c) {</p>

  <p>c.add(state);</p>

  <p>peeker = new Peeker(this, c);</p>

  <p>}</p>

  <p>public synchronized int read() { return i; }</p>

  <p>protected synchronized void update() {</p>

  <p>state.setText(getClass().getName()</p>

  <p>+ " state: i = " + i);</p>

  <p>}</p>

  <p>public void stopPeeker() {</p>

  <p>// peeker.stop(); Deprecated in Java 1.2</p>

  <p>peeker.terminate(); // The preferred approach</p>

  <p>}</p>

  <p>}</p>

  <p>class Peeker extends Thread {</p>

  <p>private Blockable b;</p>

  <p>private int session;</p>

  <p>private TextField status = new TextField(40);</p>

  <p>private boolean stop = false;</p>

  <p>public Peeker(Blockable b, Container c) {</p>

  <p>c.add(status);</p>

  <p>this.b = b;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void terminate() { stop = true; }</p>

  <p>public void run() {</p>

  <p>while (!stop) {</p>

  <p>status.setText(b.getClass().getName()</p>

  <p>+ " Peeker " + (++session)</p>

  <p>+ "; value = " + b.read());</p>

  <p>try {</p>

  <p>sleep(100);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:Continued</p>

  <p>Blockable类打算成为本例所有类的一个基础类。一个Blockable对象包含了一个名为state的TextField（文本字段），用于显示出对象有关的信息。用于显示这些信息的方法叫作update()。我们发现它用getClass.getName()来产生类名，而不是仅仅把它打印出来；这是由于update(0不知道自己为其调用的那个类的准确名字，因为那个类是从Blockable衍生出来的。</p>

  <p>在Blockable中，变动指示符是一个int i；衍生类的run()方法会为其增值。</p>

  <p>针对每个Bloackable对象，都会启动Peeker类的一个线程。Peeker的任务是调用read()方法，检查与自己关联的Blockable对象，看看i是否发生了变化，最后用它的status文本字段报告检查结果。注意read()和update()都是同步的，要求对象的锁定能自由解除，这一点非常重要。</p>

  <p>1. 睡眠</p>

  <p>这个程序的第一项测试是用sleep()作出的：</p>

  <p>///:Continuing</p>

  <p>///////////// Blocking via sleep() ///////////</p>

  <p>class Sleeper1 extends Blockable {</p>

  <p>public Sleeper1(Container c) { super(c); }</p>

  <p>public synchronized void run() {</p>

  <p>while(true) {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>try {</p>

  <p>sleep(1000);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class Sleeper2 extends Blockable {</p>

  <p>public Sleeper2(Container c) { super(c); }</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>change();</p>

  <p>try {</p>

  <p>sleep(1000);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>public synchronized void change() {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>}</p>

  <p>} ///:Continued</p>

  <p>在Sleeper1中，整个run()方法都是同步的。我们可看到与这个对象关联在一起的Peeker可以正常运行，直到我们启动线程为止，随后Peeker便会完全停止。这正是“堵塞”的一种形式：因为Sleeper1.run()是同步的，而且一旦线程启动，它就肯定在run()内部，方法永远不会放弃对象锁定，造成Peeker线程的堵塞。</p>

  <p>Sleeper2通过设置不同步的运行，提供了一种解决方案。只有change()方法才是同步的，所以尽管run()位于sleep()内部，Peeker仍然能访问自己需要的同步方法――read()。在这里，我们可看到在启动了Sleeper2线程以后，Peeker会持续运行下去。</p>

  <p>2. 暂停和恢复</p>

  <p>这个例子接下来的一部分引入了“挂起”或者“暂停”（Suspend）的概述。Thread类提供了一个名为suspend()的方法，可临时中止线程；以及一个名为resume()的方法，用于从暂停处开始恢复线程的执行。显然，我们可以推断出resume()是由暂停线程外部的某个线程调用的。在这种情况下，需要用到一个名为Resumer（恢复器）的独立类。演示暂停／恢复过程的每个类都有一个相关的恢复器。如下所示：</p>

  <p>///:Continuing</p>

  <p>/////////// Blocking via suspend() ///////////</p>

  <p>class SuspendResume extends Blockable {</p>

  <p>public SuspendResume(Container c) {</p>

  <p>super(c);</p>

  <p>new Resumer(this);</p>

  <p>}</p>

  <p>}</p>

  <p>class SuspendResume1 extends SuspendResume {</p>

  <p>public SuspendResume1(Container c) { super(c);}</p>

  <p>public synchronized void run() {</p>

  <p>while(true) {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>suspend(); // Deprecated in Java 1.2</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class SuspendResume2 extends SuspendResume {</p>

  <p>public SuspendResume2(Container c) { super(c);}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>change();</p>

  <p>suspend(); // Deprecated in Java 1.2</p>

  <p>}</p>

  <p>}</p>

  <p>public synchronized void change() {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>}</p>

  <p>}</p>

  <p>class Resumer extends Thread {</p>

  <p>private SuspendResume sr;</p>

  <p>public Resumer(SuspendResume sr) {</p>

  <p>this.sr = sr;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>try {</p>

  <p>sleep(1000);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>sr.resume(); // Deprecated in Java 1.2</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:Continued</p>

  <p>SuspendResume1也提供了一个同步的run()方法。同样地，当我们启动这个线程以后，就会发现与它关联的Peeker进入“堵塞”状态，等候对象锁被释放，但那永远不会发生。和往常一样，这个问题在SuspendResume2里得到了解决，它并不同步整个run()方法，而是采用了一个单独的同步change()方法。</p>

  <p>对于Java 1.2，大家应注意suspend()和resume()已获得强烈反对，因为suspend()包含了对象锁，所以极易出现“死锁”现象。换言之，很容易就会看到许多被锁住的对象在傻乎乎地等待对方。这会造成整个应用程序的“凝固”。尽管在一些老程序中还能看到它们的踪迹，但在你写自己的程序时，无论如何都应避免。本章稍后就会讲述正确的方案是什么。</p>

  <p>3. 等待和通知</p>

  <p>通过前两个例子的实践，我们知道无论sleep()还是suspend()都不会在自己被调用的时候解除锁定。需要用到对象锁时，请务必注意这个问题。在另一方面，wait()方法在被调用时却会解除锁定，这意味着可在执行wait()期间调用线程对象中的其他同步方法。但在接着的两个类中，我们看到run()方法都是“同步”的。在wait()期间，Peeker仍然拥有对同步方法的完全访问权限。这是由于wait()在挂起内部调用的方法时，会解除对象的锁定。</p>

  <p>我们也可以看到wait()的两种形式。第一种形式采用一个以毫秒为单位的参数，它具有与sleep()中相同的含义：暂停这一段规定时间。区别在于在wait()中，对象锁已被解除，而且能够自由地退出wait()，因为一个notify()可强行使时间流逝。</p>

  <p>第二种形式不采用任何参数，这意味着wait()会持续执行，直到notify()介入为止。而且在一段时间以后，不会自行中止。</p>

  <p>wait()和notify()比较特别的一个地方是这两个方法都属于基础类Object的一部分，不象sleep()，suspend()以及resume()那样属于Thread的一部分。尽管这表面看有点儿奇怪――居然让专门进行线程处理的东西成为通用基础类的一部分――但仔细想想又会释然，因为它们操纵的对象锁也属于每个对象的一部分。因此，我们可将一个wait()置入任何同步方法内部，无论在那个类里是否准备进行涉及线程的处理。事实上，我们能调用wait()的唯一地方是在一个同步的方法或代码块内部。若在一个不同步的方法内调用wait()或者notify()，尽管程序仍然会编译，但在运行它的时候，就会得到一个IllegalMonitorStateException（非法监视器状态违例），而且会出现多少有点莫名其妙的一条消息：“current thread not owner”（当前线程不是所有人”。注意sleep()，suspend()以及resume()都能在不同步的方法内调用，因为它们不需要对锁定进行操作。</p>

  <p>只能为自己的锁定调用wait()和notify()。同样地，仍然可以编译那些试图使用错误锁定的代码，但和往常一样会产生同样的IllegalMonitorStateException违例。我们没办法用其他人的对象锁来愚弄系统，但可要求另一个对象执行相应的操作，对它自己的锁进行操作。所以一种做法是创建一个同步方法，令其为自己的对象调用notify()。但在Notifier中，我们会看到一个同步方法内部的notify()：</p>

  <p>synchronized(wn2) {</p>

  <p>wn2.notify();</p>

  <p>}</p>

  <p>其中，wn2是类型为WaitNotify2的对象。尽管并不属于WaitNotify2的一部分，这个方法仍然获得了wn2对象的锁定。在这个时候，它为wn2调用notify()是合法的，不会得到IllegalMonitorStateException违例。</p>

  <p>///:Continuing</p>

  <p>/////////// Blocking via wait() ///////////</p>

  <p>class WaitNotify1 extends Blockable {</p>

  <p>public WaitNotify1(Container c) { super(c); }</p>

  <p>public synchronized void run() {</p>

  <p>while(true) {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>try {</p>

  <p>wait(1000);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class WaitNotify2 extends Blockable {</p>

  <p>public WaitNotify2(Container c) {</p>

  <p>super(c);</p>

  <p>new Notifier(this);</p>

  <p>}</p>

  <p>public synchronized void run() {</p>

  <p>while(true) {</p>

  <p>i++;</p>

  <p>update();</p>

  <p>try {</p>

  <p>wait();</p>

  <p>} catch (InterruptedException e){}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class Notifier extends Thread {</p>

  <p>private WaitNotify2 wn2;</p>

  <p>public Notifier(WaitNotify2 wn2) {</p>

  <p>this.wn2 = wn2;</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>try {</p>

  <p>sleep(2000);</p>

  <p>} catch (InterruptedException e){}</p>

  <p>synchronized(wn2) {</p>

  <p>wn2.notify();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:Continued</p>

  <p>若必须等候其他某些条件（从线程外部加以控制）发生变化，同时又不想在线程内一直傻乎乎地等下去，一般就需要用到wait()。wait()允许我们将线程置入“睡眠”状态，同时又“积极”地等待条件发生改变。而且只有在一个notify()或notifyAll()发生变化的时候，线程才会被唤醒，并检查条件是否有变。因此，我们认为它提供了在线程间进行同步的一种手段。</p>

  <p>4. IO堵塞</p>

  <p>若一个数据流必须等候一些IO活动，便会自动进入“堵塞”状态。在本例下面列出的部分中，有两个类协同通用的Reader以及Writer对象工作（使用Java 1.1的流）。但在测试模型中，会设置一个管道化的数据流，使两个线程相互间能安全地传递数据（这正是使用管道流的目的）。</p>

  <p>Sender将数据置入Writer，并“睡眠”随机长短的时间。然而，Receiver本身并没有包括sleep()，suspend()或者wait()方法。但在执行read()的时候，如果没有数据存在，它会自动进入“堵塞”状态。如下所示：</p>

  <p>///:Continuing</p>

  <p>class Sender extends Blockable { // send</p>

  <p>private Writer out;</p>

  <p>public Sender(Container c, Writer out) {</p>

  <p>super(c);</p>

  <p>this.out = out;</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while(true) {</p>

  <p>for(char c = 'A'; c &lt;= 'z'; c++) {</p>

  <p>try {</p>

  <p>i++;</p>

  <p>out.write(c);</p>

  <p>state.setText("Sender sent: "</p>

  <p>+ (char)c);</p>

  <p>sleep((int)(3000 * Math.random()));</p>

  <p>} catch (InterruptedException e){}</p>

  <p>catch (IOException e) {}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class Receiver extends Blockable {</p>

  <p>private Reader in;</p>

  <p>public Receiver(Container c, Reader in) {</p>

  <p>super(c);</p>

  <p>this.in = in;</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>try {</p>

  <p>while(true) {</p>

  <p>i++; // Show peeker it's alive</p>

  <p>// Blocks until characters are there:</p>

  <p>state.setText("Receiver read: "</p>

  <p>+ (char)in.read());</p>

  <p>}</p>

  <p>} catch(IOException e) { e.printStackTrace();}</p>

  <p>}</p>

  <p>} ///:Continued</p>

  <p>这两个类也将信息送入自己的state字段，并修改i值，使Peeker知道线程仍在运行。</p>

  <p>5. 测试</p>

  <p>令人惊讶的是，主要的程序片（Applet）类非常简单，这是大多数工作都已置入Blockable框架的缘故。大概地说，我们创建了一个由Blockable对象构成的数组。而且由于每个对象都是一个线程，所以在按下“start”按钮后，它们会采取自己的行动。还有另一个按钮和actionPerformed()从句，用于中止所有Peeker对象。由于Java 1.2“反对”使用Thread的stop()方法，所以可考虑采用这种折衷形式的中止方式。</p>

  <p>为了在Sender和Receiver之间建立一个连接，我们创建了一个PipedWriter和一个PipedReader。注意PipedReader in必须通过一个构建器参数同PipedWriterout连接起来。在那以后，我们在out内放进去的所有东西都可从in中提取出来――似乎那些东西是通过一个“管道”传输过去的。随后将in和out对象分别传递给Receiver和Sender构建器；后者将它们当作任意类型的Reader和Writer看待（也就是说，它们被“上溯”造型了）。</p>

  <p>Blockable句柄b的数组在定义之初并未得到初始化，因为管道化的数据流是不可在定义前设置好的（对try块的需要将成为障碍）：</p>

  <p>///:Continuing</p>

  <p>/////////// Testing Everything ///////////</p>

  <p>public class Blocking extends Applet {</p>

  <p>private Button</p>

  <p>start = new Button("Start"),</p>

  <p>stopPeekers = new Button("Stop Peekers");</p>

  <p>private boolean started = false;</p>

  <p>private Blockable[] b;</p>

  <p>private PipedWriter out;</p>

  <p>private PipedReader in;</p>

  <p>public void init() {</p>

  <p>out = new PipedWriter();</p>

  <p>try {</p>

  <p>in = new PipedReader(out);</p>

  <p>} catch(IOException e) {}</p>

  <p>b = new Blockable[] {</p>

  <p>new Sleeper1(this),</p>

  <p>new Sleeper2(this),</p>

  <p>new SuspendResume1(this),</p>

  <p>new SuspendResume2(this),</p>

  <p>new WaitNotify1(this),</p>

  <p>new WaitNotify2(this),</p>

  <p>new Sender(this, out),</p>

  <p>new Receiver(this, in)</p>

  <p>};</p>

  <p>start.addActionListener(new StartL());</p>

  <p>add(start);</p>

  <p>stopPeekers.addActionListener(</p>

  <p>new StopPeekersL());</p>

  <p>add(stopPeekers);</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(!started) {</p>

  <p>started = true;</p>

  <p>for(int i = 0; i &lt; b.length; i++)</p>

  <p>b[i].start();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class StopPeekersL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>// Demonstration of the preferred</p>

  <p>// alternative to Thread.stop():</p>

  <p>for(int i = 0; i &lt; b.length; i++)</p>

  <p>b[i].stopPeeker();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Blocking applet = new Blocking();</p>

  <p>Frame aFrame = new Frame("Blocking");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(350,550);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在init()中，注意循环会遍历整个数组，并为页添加state和peeker.status文本字段。</p>

  <p>首次创建好Blockable线程以后，每个这样的线程都会自动创建并启动自己的Peeker。所以我们会看到各个Peeker都在Blockable线程启动之前运行起来。这一点非常重要，因为在Blockable线程启动的时候，部分Peeker会被堵塞，并停止运行。弄懂这一点，将有助于我们加深对“堵塞”这一概念的认识。</p>

  <p>14.3.2 死锁</p>

  <p>由于线程可能进入堵塞状态，而且由于对象可能拥有“同步”方法――除非同步锁定被解除，否则线程不能访问那个对象――所以一个线程完全可能等候另一个对象，而另一个对象又在等候下一个对象，以此类推。这个“等候”链最可怕的情形就是进入封闭状态――最后那个对象等候的是第一个对象！此时，所有线程都会陷入无休止的相互等待状态，大家都动弹不得。我们将这种情况称为“死锁”。尽管这种情况并非经常出现，但一旦碰到，程序的调试将变得异常艰难。</p>

  <p>就语言本身来说，尚未直接提供防止死锁的帮助措施，需要我们通过谨慎的设计来避免。如果有谁需要调试一个死锁的程序，他是没有任何窍门可用的。</p>

  <p>1. Java 1.2对stop()，suspend()，resume()以及destroy()的反对</p>

  <p>为减少出现死锁的可能，Java 1.2作出的一项贡献是“反对”使用Thread的stop()，suspend()，resume()以及destroy()方法。</p>

  <p>之所以反对使用stop()，是因为它不安全。它会解除由线程获取的所有锁定，而且如果对象处于一种不连贯状态（“被破坏”），那么其他线程能在那种状态下检查和修改它们。结果便造成了一种微妙的局面，我们很难检查出真正的问题所在。所以应尽量避免使用stop()，应该采用Blocking.java那样的方法，用一个标志告诉线程什么时候通过退出自己的run()方法来中止自己的执行。</p>

  <p>如果一个线程被堵塞，比如在它等候输入的时候，那么一般都不能象在Blocking.java中那样轮询一个标志。但在这些情况下，我们仍然不该使用stop()，而应换用由Thread提供的interrupt()方法，以便中止并退出堵塞的代码。</p>

  <p>//: Interrupt.java</p>

  <p>// The alternative approach to using stop()</p>

  <p>// when a thread is blocked</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class Blocked extends Thread {</p>

  <p>public synchronized void run() {</p>

  <p>try {</p>

  <p>wait(); // Blocks</p>

  <p>} catch(InterruptedException e) {</p>

  <p>System.out.println("InterruptedException");</p>

  <p>}</p>

  <p>System.out.println("Exiting run()");</p>

  <p>}</p>

  <p>}</p>

  <p>public class Interrupt extends Applet {</p>

  <p>private Button</p>

  <p>interrupt = new Button("Interrupt");</p>

  <p>private Blocked blocked = new Blocked();</p>

  <p>public void init() {</p>

  <p>add(interrupt);</p>

  <p>interrupt.addActionListener(</p>

  <p>new ActionListener() {</p>

  <p>public</p>

  <p>void actionPerformed(ActionEvent e) {</p>

  <p>System.out.println("Button pressed");</p>

  <p>if(blocked == null) return;</p>

  <p>Thread remove = blocked;</p>

  <p>blocked = null; // to release it</p>

  <p>remove.interrupt();</p>

  <p>}</p>

  <p>});</p>

  <p>blocked.start();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Interrupt applet = new Interrupt();</p>

  <p>Frame aFrame = new Frame("Interrupt");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(200,100);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Blocked.run()内部的wait()会产生堵塞的线程。当我们按下按钮以后，blocked（堵塞）的句柄就会设为null，使垃圾收集器能够将其清除，然后调用对象的interrupt()方法。如果是首次按下按钮，我们会看到线程正常退出。但在没有可供“杀死”的线程以后，看到的便只是按钮被按下而已。</p>

  <p>suspend()和resume()方法天生容易发生死锁。调用suspend()的时候，目标线程会停下来，但却仍然持有在这之前获得的锁定。此时，其他任何线程都不能访问锁定的资源，除非被“挂起”的线程恢复运行。对任何线程来说，如果它们想恢复目标线程，同时又试图使用任何一个锁定的资源，就会造成令人难堪的死锁。所以我们不应该使用suspend()和resume()，而应在自己的Thread类中置入一个标志，指出线程应该活动还是挂起。若标志指出线程应该挂起，便用wait()命其进入等待状态。若标志指出线程应当恢复，则用一个notify()重新启动线程。我们可以修改前面的Counter2.java来实际体验一番。尽管两个版本的效果是差不多的，但大家会注意到代码的组织结构发生了很大的变化――为所有“听众”都使用了匿名的内部类，而且Thread是一个内部类。这使得程序的编写稍微方便一些，因为它取消了Counter2.java中一些额外的记录工作。</p>

  <p>//: Suspend.java</p>

  <p>// The alternative approach to using suspend()</p>

  <p>// and resume(), which have been deprecated</p>

  <p>// in Java 1.2.</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>public class Suspend extends Applet {</p>

  <p>private TextField t = new TextField(10);</p>

  <p>private Button</p>

  <p>suspend = new Button("Suspend"),</p>

  <p>resume = new Button("Resume");</p>

  <p>class Suspendable extends Thread {</p>

  <p>private int count = 0;</p>

  <p>private boolean suspended = false;</p>

  <p>public Suspendable() { start(); }</p>

  <p>public void fauxSuspend() {</p>

  <p>suspended = true;</p>

  <p>}</p>

  <p>public synchronized void fauxResume() {</p>

  <p>suspended = false;</p>

  <p>notify();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>try {</p>

  <p>sleep(100);</p>

  <p>synchronized(this) {</p>

  <p>while(suspended)</p>

  <p>wait();</p>

  <p>}</p>

  <p>} catch (InterruptedException e){}</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>private Suspendable ss = new Suspendable();</p>

  <p>public void init() {</p>

  <p>add(t);</p>

  <p>suspend.addActionListener(</p>

  <p>new ActionListener() {</p>

  <p>public</p>

  <p>void actionPerformed(ActionEvent e) {</p>

  <p>ss.fauxSuspend();</p>

  <p>}</p>

  <p>});</p>

  <p>add(suspend);</p>

  <p>resume.addActionListener(</p>

  <p>new ActionListener() {</p>

  <p>public</p>

  <p>void actionPerformed(ActionEvent e) {</p>

  <p>ss.fauxResume();</p>

  <p>}</p>

  <p>});</p>

  <p>add(resume);</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Suspend applet = new Suspend();</p>

  <p>Frame aFrame = new Frame("Suspend");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e){</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300,100);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Suspendable中的suspended（已挂起）标志用于开关“挂起”或者“暂停”状态。为挂起一个线程，只需调用fauxSuspend()将标志设为true（真）即可。对标志状态的侦测是在run()内进行的。就象本章早些时候提到的那样，wait()必须设为“同步”（synchronized），使其能够使用对象锁。在fauxResume()中，suspended标志被设为false（假），并调用notify()――由于这会在一个“同步”从句中唤醒wait()，所以fauxResume()方法也必须同步，使其能在调用notify()之前取得对象锁（这样一来，对象锁可由要唤醍的那个wait()使用）。如果遵照本程序展示的样式，可以避免使用wait()和notify()。</p>

  <p>Thread的destroy()方法根本没有实现；它类似一个根本不能恢复的suspend()，所以会发生与suspend()一样的死锁问题。然而，这一方法没有得到明确的“反对”，也许会在Java以后的版本（1.2版以后）实现，用于一些可以承受死锁危险的特殊场合。</p>

  <p>大家可能会奇怪当初为什么要实现这些现在又被“反对”的方法。之所以会出现这种情况，大概是由于Sun公司主要让技术人员来决定对语言的改动，而不是那些市场销售人员。通常，技术人员比搞销售的更能理解语言的实质。当初犯下了错误以后，也能较为理智地正视它们。这意味着Java能够继续进步，即便这使Java程序员多少感到有些不便。就我自己来说，宁愿面对这些不便之处，也不愿看到语言停滞不前。</p>

  <p>14.4 优先级</p>

  <p>线程的优先级（Priority）告诉调试程序该线程的重要程度有多大。如果有大量线程都被堵塞，都在等候运行，调试程序会首先运行具有最高优先级的那个线程。然而，这并不表示优先级较低的线程不会运行（换言之，不会因为存在优先级而导致死锁）。若线程的优先级较低，只不过表示它被准许运行的机会小一些而已。</p>

  <p>可用getPriority()方法读取一个线程的优先级，并用setPriority()改变它。在下面这个程序片中，大家会发现计数器的计数速度慢了下来，因为它们关联的线程分配了较低的优先级：</p>

  <p>//: Counter5.java</p>

  <p>// Adjusting the priorities of threads</p>

  <p>import java.awt.*;</p>

  <p>import java.awt.event.*;</p>

  <p>import java.applet.*;</p>

  <p>class Ticker2 extends Thread {</p>

  <p>private Button</p>

  <p>b = new Button("Toggle"),</p>

  <p>incPriority = new Button("up"),</p>

  <p>decPriority = new Button("down");</p>

  <p>private TextField</p>

  <p>t = new TextField(10),</p>

  <p>pr = new TextField(3); // Display priority</p>

  <p>private int count = 0;</p>

  <p>private boolean runFlag = true;</p>

  <p>public Ticker2(Container c) {</p>

  <p>b.addActionListener(new ToggleL());</p>

  <p>incPriority.addActionListener(new UpL());</p>

  <p>decPriority.addActionListener(new DownL());</p>

  <p>Panel p = new Panel();</p>

  <p>p.add(t);</p>

  <p>p.add(pr);</p>

  <p>p.add(b);</p>

  <p>p.add(incPriority);</p>

  <p>p.add(decPriority);</p>

  <p>c.add(p);</p>

  <p>}</p>

  <p>class ToggleL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>runFlag = !runFlag;</p>

  <p>}</p>

  <p>}</p>

  <p>class UpL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>int newPriority = getPriority() + 1;</p>

  <p>if(newPriority &gt; Thread.MAX_PRIORITY)</p>

  <p>newPriority = Thread.MAX_PRIORITY;</p>

  <p>setPriority(newPriority);</p>

  <p>}</p>

  <p>}</p>

  <p>class DownL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>int newPriority = getPriority() - 1;</p>

  <p>if(newPriority &lt; Thread.MIN_PRIORITY)</p>

  <p>newPriority = Thread.MIN_PRIORITY;</p>

  <p>setPriority(newPriority);</p>

  <p>}</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>while (true) {</p>

  <p>if(runFlag) {</p>

  <p>t.setText(Integer.toString(count++));</p>

  <p>pr.setText(</p>

  <p>Integer.toString(getPriority()));</p>

  <p>}</p>

  <p>yield();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Counter5 extends Applet {</p>

  <p>private Button</p>

  <p>start = new Button("Start"),</p>

  <p>upMax = new Button("Inc Max Priority"),</p>

  <p>downMax = new Button("Dec Max Priority");</p>

  <p>private boolean started = false;</p>

  <p>private static final int SIZE = 10;</p>

  <p>private Ticker2[] s = new Ticker2[SIZE];</p>

  <p>private TextField mp = new TextField(3);</p>

  <p>public void init() {</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i] = new Ticker2(this);</p>

  <p>add(new Label("MAX_PRIORITY = "</p>

  <p>+ Thread.MAX_PRIORITY));</p>

  <p>add(new Label("MIN_PRIORITY = "</p>

  <p>+ Thread.MIN_PRIORITY));</p>

  <p>add(new Label("Group Max Priority = "));</p>

  <p>add(mp);</p>

  <p>add(start);</p>

  <p>add(upMax); add(downMax);</p>

  <p>start.addActionListener(new StartL());</p>

  <p>upMax.addActionListener(new UpMaxL());</p>

  <p>downMax.addActionListener(new DownMaxL());</p>

  <p>showMaxPriority();</p>

  <p>// Recursively display parent thread groups:</p>

  <p>ThreadGroup parent =</p>

  <p>s[0].getThreadGroup().getParent();</p>

  <p>while(parent != null) {</p>

  <p>add(new Label(</p>

  <p>"Parent threadgroup max priority = "</p>

  <p>+ parent.getMaxPriority()));</p>

  <p>parent = parent.getParent();</p>

  <p>}</p>

  <p>}</p>

  <p>public void showMaxPriority() {</p>

  <p>mp.setText(Integer.toString(</p>

  <p>s[0].getThreadGroup().getMaxPriority()));</p>

  <p>}</p>

  <p>class StartL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>if(!started) {</p>

  <p>started = true;</p>

  <p>for(int i = 0; i &lt; s.length; i++)</p>

  <p>s[i].start();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>class UpMaxL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>int maxp =</p>

  <p>s[0].getThreadGroup().getMaxPriority();</p>

  <p>if(++maxp &gt; Thread.MAX_PRIORITY)</p>

  <p>maxp = Thread.MAX_PRIORITY;</p>

  <p>s[0].getThreadGroup().setMaxPriority(maxp);</p>

  <p>showMaxPriority();</p>

  <p>}</p>

  <p>}</p>

  <p>class DownMaxL implements ActionListener {</p>

  <p>public void actionPerformed(ActionEvent e) {</p>

  <p>int maxp =</p>

  <p>s[0].getThreadGroup().getMaxPriority();</p>

  <p>if(--maxp &lt; Thread.MIN_PRIORITY)</p>

  <p>maxp = Thread.MIN_PRIORITY;</p>

  <p>s[0].getThreadGroup().setMaxPriority(maxp);</p>

  <p>showMaxPriority();</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Counter5 applet = new Counter5();</p>

  <p>Frame aFrame = new Frame("Counter5");</p>

  <p>aFrame.addWindowListener(</p>

  <p>new WindowAdapter() {</p>

  <p>public void windowClosing(WindowEvent e) {</p>

  <p>System.exit(0);</p>

  <p>}</p>

  <p>});</p>

  <p>aFrame.add(applet, BorderLayout.CENTER);</p>

  <p>aFrame.setSize(300, 600);</p>

  <p>applet.init();</p>

  <p>applet.start();</p>

  <p>aFrame.setVisible(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>Ticker采用本章前面构造好的形式，但有一个额外的TextField（文本字段），用于显示线程的优先级；以及两个额外的按钮，用于人为提高及降低优先级。</p>

  <p>也要注意yield()的用法，它将控制权自动返回给调试程序（机制）。若不进行这样的处理，多线程机制仍会工作，但我们会发现它的运行速度慢了下来（试试删去对yield()的调用）。亦可调用sleep()，但假若那样做，计数频率就会改由sleep()的持续时间控制，而不是优先级。</p>

  <p>Counter5中的init()创建了由10个Ticker2构成的一个数组；它们的按钮以及输入字段（文本字段）由Ticker2构建器置入窗体。Counter5增加了新的按钮，用于启动一切，以及用于提高和降低线程组的最大优先级。除此以外，还有一些标签用于显示一个线程可以采用的最大及最小优先级；以及一个特殊的文本字段，用于显示线程组的最大优先级（在下一节里，我们将全面讨论线程组的问题）。最后，父线程组的优先级也作为标签显示出来。</p>

  <p>按下“up”（上）或“down”（下）按钮的时候，会先取得Ticker2当前的优先级，然后相应地提高或者降低。</p>

  <p>运行该程序时，我们可注意到几件事情。首先，线程组的默认优先级是5。即使在启动线程之前（或者在创建线程之前，这要求对代码进行适当的修改）将最大优先级降到5以下，每个线程都会有一个5的默认优先级。</p>

  <p>最简单的测试是获取一个计数器，将它的优先级降低至1，此时应观察到它的计数频率显著放慢。现在试着再次提高优先级，可以升高回线程组的优先级，但不能再高了。现在将线程组的优先级降低两次。线程的优先级不会改变，但假若试图提高或者降低它，就会发现这个优先级自动变成线程组的优先级。此外，新线程仍然具有一个默认优先级，即使它比组的优先级还要高（换句话说，不要指望利用组优先级来防止新线程拥有比现有的更高的优先级）。</p>

  <p>最后，试着提高组的最大优先级。可以发现，这样做是没有效果的。我们只能减少线程组的最大优先级，而不能增大它。</p>

  <p>14.4.1 线程组</p>

  <p>所有线程都隶属于一个线程组。那可以是一个默认线程组，亦可是一个创建线程时明确指定的组。在创建之初，线程被限制到一个组里，而且不能改变到一个不同的组。每个应用都至少有一个线程从属于系统线程组。若创建多个线程而不指定一个组，它们就会自动归属于系统线程组。</p>

  <p>线程组也必须从属于其他线程组。必须在构建器里指定新线程组从属于哪个线程组。若在创建一个线程组的时候没有指定它的归属，则同样会自动成为系统线程组的一名属下。因此，一个应用程序中的所有线程组最终都会将系统线程组作为自己的“父”。</p>

  <p>之所以要提出“线程组”的概念，很难从字面上找到原因。这多少为我们讨论的主题带来了一些混乱。一般地说，我们认为是由于“安全”或者“保密”方面的理由才使用线程组的。根据Arnold和Gosling的说法：“线程组中的线程可以修改组内的其他线程，包括那些位于分层结构最深处的。一个线程不能修改位于自己所在组或者下属组之外的任何线程”（注释①）。然而，我们很难判断“修改”在这儿的具体含义是什么。下面这个例子展示了位于一个“叶子组”内的线程能修改它所在线程组树的所有线程的优先级，同时还能为这个“树”内的所有线程都调用一个方法。</p>

  <p>①：《The Java Programming Language》第179页。该书由Arnold和Jams Gosling编著，Addison-Wesley于1996年出版</p>

  <p>//: TestAccess.java</p>

  <p>// How threads can access other threads</p>

  <p>// in a parent thread group</p>

  <p>public class TestAccess {</p>

  <p>public static void main(String[] args) {</p>

  <p>ThreadGroup</p>

  <p>x = new ThreadGroup("x"),</p>

  <p>y = new ThreadGroup(x, "y"),</p>

  <p>z = new ThreadGroup(y, "z");</p>

  <p>Thread</p>

  <p>one = new TestThread1(x, "one"),</p>

  <p>two = new TestThread2(z, "two");</p>

  <p>}</p>

  <p>}</p>

  <p>class TestThread1 extends Thread {</p>

  <p>private int i;</p>

  <p>TestThread1(ThreadGroup g, String name) {</p>

  <p>super(g, name);</p>

  <p>}</p>

  <p>void f() {</p>

  <p>i++; // modify this thread</p>

  <p>System.out.println(getName() + " f()");</p>

  <p>}</p>

  <p>}</p>

  <p>class TestThread2 extends TestThread1 {</p>

  <p>TestThread2(ThreadGroup g, String name) {</p>

  <p>super(g, name);</p>

  <p>start();</p>

  <p>}</p>

  <p>public void run() {</p>

  <p>ThreadGroup g =</p>

  <p>getThreadGroup().getParent().getParent();</p>

  <p>g.list();</p>

  <p>Thread[] gAll = new Thread[g.activeCount()];</p>

  <p>g.enumerate(gAll);</p>

  <p>for(int i = 0; i &lt; gAll.length; i++) {</p>

  <p>gAll[i].setPriority(Thread.MIN_PRIORITY);</p>

  <p>((TestThread1)gAll[i]).f();</p>

  <p>}</p>

  <p>g.list();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在main()中，我们创建了几个ThreadGroup（线程组），每个都位于不同的“叶”上：x没有参数，只有它的名字（一个String），所以会自动进入“system”（系统）线程组；y位于x下方，而z位于y下方。注意初始化是按照文字顺序进行的，所以代码合法。</p>

  <p>有两个线程创建之后进入了不同的线程组。其中，TestThread1没有一个run()方法，但有一个f()，用于通知线程以及打印出一些东西，以便我们知道它已被调用。而TestThread2属于TestThread1的一个子类，它的run()非常详尽，要做许多事情。首先，它获得当前线程所在的线程组，然后利用getParent()在继承树中向上移动两级（这样做是有道理的，因为我想把TestThread2在分级结构中向下移动两级）。随后，我们调用方法activeCount()，查询这个线程组以及所有子线程组内有多少个线程，从而创建由指向Thread的句柄构成的一个数组。enumerate()方法将指向所有这些线程的句柄置入数组gAll里。然后在整个数组里遍历，为每个线程都调用f()方法，同时修改优先级。这样一来，位于一个“叶子”线程组里的线程就修改了位于父线程组的线程。</p>

  <p>调试方法list()打印出与一个线程组有关的所有信息，把它们作为标准输出。在我们对线程组的行为进行调查的时候，这样做是相当有好处的。下面是程序的输出：</p>

  <p>java.lang.ThreadGroup[name=x,maxpri=10]</p>

  <p>Thread[one,5,x]</p>

  <p>java.lang.ThreadGroup[name=y,maxpri=10]</p>

  <p>java.lang.ThreadGroup[name=z,maxpri=10]</p>

  <p>Thread[two,5,z]</p>

  <p>one f()</p>

  <p>two f()</p>

  <p>java.lang.ThreadGroup[name=x,maxpri=10]</p>

  <p>Thread[one,1,x]</p>

  <p>java.lang.ThreadGroup[name=y,maxpri=10]</p>

  <p>java.lang.ThreadGroup[name=z,maxpri=10]</p>

  <p>Thread[two,1,z]</p>

  <p>list()不仅打印出ThreadGroup或者Thread的类名，也打印出了线程组的名字以及它的最高优先级。对于线程，则打印出它们的名字，并接上线程优先级以及所属的线程组。注意list()会对线程和线程组进行缩排处理，指出它们是未缩排的线程组的“子”。</p>

  <p>大家可看到f()是由TestThread2的run()方法调用的，所以很明显，组内的所有线程都是相当脆弱的。然而，我们只能访问那些从自己的system线程组树分支出来的线程，而且或许这就是所谓“安全”的意思。我们不能访问其他任何人的系统线程树。</p>

  <p>1. 线程组的控制</p>

  <p>抛开安全问题不谈，线程组最有用的一个地方就是控制：只需用单个命令即可完成对整个线程组的操作。下面这个例子演示了这一点，并对线程组内优先级的限制进行了说明。括号内的注释数字便于大家比较输出结果：</p>

  <p>//: ThreadGroup1.java</p>

  <p>// How thread groups control priorities</p>

  <p>// of the threads inside them.</p>

  <p>public class ThreadGroup1 {</p>

  <p>public static void main(String[] args) {</p>

  <p>// Get the system thread &amp; print its Info:</p>

  <p>ThreadGroup sys =</p>

  <p>Thread.currentThread().getThreadGroup();</p>

  <p>sys.list(); // (1)</p>

  <p>// Reduce the system thread group priority:</p>

  <p>sys.setMaxPriority(Thread.MAX_PRIORITY - 1);</p>

  <p>// Increase the main thread priority:</p>

  <p>Thread curr = Thread.currentThread();</p>

  <p>curr.setPriority(curr.getPriority() + 1);</p>

  <p>sys.list(); // (2)</p>

  <p>// Attempt to set a new group to the max:</p>

  <p>ThreadGroup g1 = new ThreadGroup("g1");</p>

  <p>g1.setMaxPriority(Thread.MAX_PRIORITY);</p>

  <p>// Attempt to set a new thread to the max:</p>

  <p>Thread t = new Thread(g1, "A");</p>

  <p>t.setPriority(Thread.MAX_PRIORITY);</p>

  <p>g1.list(); // (3)</p>

  <p>// Reduce g1's max priority, then attempt</p>

  <p>// to increase it:</p>

  <p>g1.setMaxPriority(Thread.MAX_PRIORITY - 2);</p>

  <p>g1.setMaxPriority(Thread.MAX_PRIORITY);</p>

  <p>g1.list(); // (4)</p>

  <p>// Attempt to set a new thread to the max:</p>

  <p>t = new Thread(g1, "B");</p>

  <p>t.setPriority(Thread.MAX_PRIORITY);</p>

  <p>g1.list(); // (5)</p>

  <p>// Lower the max priority below the default</p>

  <p>// thread priority:</p>

  <p>g1.setMaxPriority(Thread.MIN_PRIORITY + 2);</p>

  <p>// Look at a new thread's priority before</p>

  <p>// and after changing it:</p>

  <p>t = new Thread(g1, "C");</p>

  <p>g1.list(); // (6)</p>

  <p>t.setPriority(t.getPriority() -1);</p>

  <p>g1.list(); // (7)</p>

  <p>// Make g2 a child Threadgroup of g1 and</p>

  <p>// try to increase its priority:</p>

  <p>ThreadGroup g2 = new ThreadGroup(g1, "g2");</p>

  <p>g2.list(); // (8)</p>

  <p>g2.setMaxPriority(Thread.MAX_PRIORITY);</p>

  <p>g2.list(); // (9)</p>

  <p>// Add a bunch of new threads to g2:</p>

  <p>for (int i = 0; i &lt; 5; i++)</p>

  <p>new Thread(g2, Integer.toString(i));</p>

  <p>// Show information about all threadgroups</p>

  <p>// and threads:</p>

  <p>sys.list(); // (10)</p>

  <p>System.out.println("Starting all threads:");</p>

  <p>Thread[] all = new Thread[sys.activeCount()];</p>

  <p>sys.enumerate(all);</p>

  <p>for</p>

  <div class="mbppagebreak"></div>
</body>
</html>
